/*
 * st-hack.h
 *
 *  Created on: Dec 29, 2015
 *      Author: zhangzm
 */

#ifndef ST_HACK_H_
#define ST_HACK_H_
#include "ursax.h"
#include "st.h"

#define LIMIT_CONNECT_TIME (60)

typedef struct _st_netfd_hack{
	st_mutex_t read_lock;
	st_mutex_t write_lock;
	uint32_t ip;
	uint16_t port;
	uint16_t reference;
	bool socket_error;
} st_netfd_hack_t;

#define GET_HACK_NONE_RETURN(hack, fd, cmd)		\
	st_netfd_hack_t *hack = (st_netfd_hack_t*)st_netfd_getspecific(fd); \
	if(!hack){									\
		cmd;									\
	}

static inline void _put_write_lock(st_netfd_t fd)
{
	GET_HACK_NONE_RETURN(hack, fd, return);
	if(hack->write_lock){
		LOG_DEBUG("_put_write_lock %p", fd);
		st_mutex_unlock(hack->write_lock);
	}
}

static inline void _get_write_lock(st_netfd_t fd)
{
	GET_HACK_NONE_RETURN(hack, fd, return);
	if(hack->write_lock){
		LOG_DEBUG("_get_write_lock %p", fd);
		st_mutex_lock(hack->write_lock);
	}
}

static inline void _put_read_lock(st_netfd_t fd)
{
	GET_HACK_NONE_RETURN(hack, fd, return);
	if(hack->read_lock){
		LOG_DEBUG("_put_read_lock %p", fd);
		st_mutex_unlock(hack->read_lock);
	}
}

static inline void _get_read_lock(st_netfd_t fd)
{
	GET_HACK_NONE_RETURN(hack, fd, return);
	if(hack->read_lock){
		LOG_DEBUG("_get_read_lock %p", fd);
		st_mutex_lock(hack->read_lock);
	}
}

static inline void _inc_st_reference(st_netfd_t fd)
{
	GET_HACK_NONE_RETURN(hack, fd, return);
	hack->reference++;
}

static inline void _dec_st_reference(st_netfd_t fd)
{
	GET_HACK_NONE_RETURN(hack, fd, return);
	hack->reference--;
}

static inline uint16_t _get_st_reference(st_netfd_t fd)
{
	GET_HACK_NONE_RETURN(hack, fd, return 0);
	return hack->reference;
}

typedef void  (*__st_destructor_t)(void *);
static inline void st_alloc_rw_lock(st_netfd_t fd, uint32_t ip, uint16_t port)
{
	st_netfd_hack_t *hack = (st_netfd_hack_t*)zalloc(sizeof(*hack));
	st_netfd_setspecific(fd, (void*)hack, (__st_destructor_t)0);
	hack->write_lock = st_mutex_new();
	hack->read_lock = st_mutex_new();
	hack->ip = ip;
	hack->port = port;
	LOG_DEBUG("st_alloc_rw_lock %p, addr %s:%d", fd, str_ip(ip), port);
}

static inline void st_free_rw_lock(st_netfd_t fd)
{
	st_netfd_hack_t *hack = (st_netfd_hack_t*)st_netfd_getspecific(fd);
	st_netfd_setspecific(fd, (void*)0, (__st_destructor_t)0);
	if(hack){
		if(hack->write_lock){
			st_mutex_destroy(hack->write_lock);
		}
		if(hack->read_lock){
			st_mutex_destroy(hack->read_lock);
		}
		free(hack);
		LOG_DEBUG("st_free_rw_lock %p", fd);
	}
}

static inline void st_get_read_lock(st_netfd_t fd)
{
	if(!fd){
		return;
	}
	_get_read_lock(fd);
}

static inline void st_put_read_lock(st_netfd_t fd)
{
	if(!fd){
		return;
	}
	_put_read_lock(fd);
}

static inline void st_get_write_lock(st_netfd_t fd)
{
	if(!fd){
		return;
	}
	_get_write_lock(fd);
}

static inline void st_put_write_lock(st_netfd_t fd)
{
	if(!fd){
		return;
	}
	_put_write_lock(fd);
}

static inline bool st_is_socket_limit(st_netfd_t stfd)
{
	return !!((uint64_t)stfd & (1));
}

static inline void* st_now_limit_value(void)
{
	time_t now = time(0);
	return (void*)(((uint64_t)now<<1) | 1);
}

static inline void st_set_limit(st_netfd_t *stfd)
{
	*stfd = (st_netfd_t)st_now_limit_value();
}

static inline bool st_is_limit_expire(st_netfd_t stfd)
{
	time_t stored_time = (time_t)((uint64_t)stfd>>1);
	time_t now = (time_t)(((uint64_t)time(0)<<1)>>1);
	if(now - stored_time > LIMIT_CONNECT_TIME){
		LOG_INFO("now is %ld stored time is %ld limit expired", now, stored_time);
		return true;
	}
	return false;
}

static inline bool st_is_socket_error(st_netfd_t stfd)
{
	if(!stfd){
		// stfd is NULL, just return true, error
		return true;
	}
	if(st_is_socket_limit(stfd)){
		return true;
	}
	st_netfd_hack_t *hack = (st_netfd_hack_t*)st_netfd_getspecific(stfd);
	if(unlikely(!hack)){
		return false;
	}
	return hack->socket_error;
}

#define CHECK_SOCKET_ERROR_RETURN(stfd) 												\
	{																					\
		if(unlikely(st_is_socket_error(stfd))) 											\
		{																				\
			LOG_WARN("socket %p is error, return -1 here", stfd);						\
			if(stfd){																	\
				st_netfd_hack_t *__hack = (st_netfd_hack_t*)st_netfd_getspecific(stfd);	\
				LOG_WARN("error addr is %s:%d", str_ip(__hack->ip), __hack->port);		\
			}																			\
			return -1;																	\
		}else{ 																			\
		}																				\
	}


static inline ssize_t hack_st_writev(st_netfd_t fd, const struct iovec *iov, int iov_size,
		  st_utime_t timeout)
{
	_put_read_lock(fd);
	_get_write_lock(fd);
	CHECK_SOCKET_ERROR_RETURN(fd);
	return st_writev(fd, iov, iov_size, timeout);
}

static inline int hack_st_writev_resid(st_netfd_t fd, struct iovec **iov, int *iov_size,
		    st_utime_t timeout)
{
	_put_read_lock(fd);
	_get_write_lock(fd);
	CHECK_SOCKET_ERROR_RETURN(fd);
	return st_writev_resid(fd, iov, iov_size, timeout);
}

static inline ssize_t hack_st_write(st_netfd_t fd, const void *buf, size_t nbyte,
		st_utime_t timeout)
{
	_put_read_lock(fd);
	_get_write_lock(fd);
	CHECK_SOCKET_ERROR_RETURN(fd);
	return st_write(fd, buf, nbyte, timeout);
}

static inline int hack_st_write_resid(st_netfd_t fd, const void *buf, size_t *resid,
		  st_utime_t timeout)
{
	_put_read_lock(fd);
	_get_write_lock(fd);
	CHECK_SOCKET_ERROR_RETURN(fd);
	return st_write_resid(fd, buf, resid, timeout);
}


static inline ssize_t hack_st_read(st_netfd_t fd, void *buf, size_t nbyte, st_utime_t timeout)
{
	_put_write_lock(fd);
	_get_read_lock(fd);
	CHECK_SOCKET_ERROR_RETURN(fd);
	return st_read(fd, buf, nbyte, timeout);
}

static inline int hack_st_read_resid(st_netfd_t fd, void *buf, size_t *resid,
		 st_utime_t timeout)
{
	_put_write_lock(fd);
	_get_read_lock(fd);
	CHECK_SOCKET_ERROR_RETURN(fd);
	return st_read_resid(fd, buf, resid, timeout);
}

static inline ssize_t hack_st_readv(st_netfd_t fd, const struct iovec *iov, int iov_size,
		 st_utime_t timeout)
{
	_put_write_lock(fd);
	_get_read_lock(fd);
	CHECK_SOCKET_ERROR_RETURN(fd);
	return st_readv(fd, iov, iov_size, timeout);
}

static inline int hack_st_readv_resid(st_netfd_t fd, struct iovec **iov, int *iov_size,
		   st_utime_t timeout)
{
	_put_write_lock(fd);
	_get_read_lock(fd);
	CHECK_SOCKET_ERROR_RETURN(fd);
	return st_readv_resid(fd, iov, iov_size, timeout);
}

static inline ssize_t hack_st_read_fully(st_netfd_t fd, void *buf, size_t nbyte,
	     st_utime_t timeout)
{
	_put_write_lock(fd);
	_get_read_lock(fd);
	CHECK_SOCKET_ERROR_RETURN(fd);
	return st_read_fully(fd, buf, nbyte, timeout);
}

static inline void st_set_socket_error(st_netfd_t stfd)
{
	st_netfd_hack_t *hack = (st_netfd_hack_t*)st_netfd_getspecific(stfd);
	if(unlikely(!hack)){
		LOG_WARN("st_set_socket_error hack is NULL");
		return;
	}
	hack->socket_error = true;
}

static inline void st_clear_socket_error(st_netfd_t stfd)
{
	st_netfd_hack_t *hack = (st_netfd_hack_t*)st_netfd_getspecific(stfd);
	if(unlikely(!hack)){
		LOG_DEBUG("st_clear_socket_error hack is NULL");
		return;
	}
	hack->socket_error = false;
}

#define st_writev(...) 			hack_st_writev(__VA_ARGS__)
#define st_writev_resid(...) 	hack_st_writev_resid(__VA_ARGS__)
#define st_write(...)			hack_st_write(__VA_ARGS__)
#define st_write_resid(...)		hack_st_write_resid(__VA_ARGS__)
#define st_read(...) 			hack_st_read(__VA_ARGS__)
#define st_read_resid(...)		hack_st_read_resid(__VA_ARGS__)
#define st_readv(...) 			hack_st_readv(__VA_ARGS__)
#define st_readv_resid(...) 	hack_st_readv_resid(__VA_ARGS__)
#define st_read_fully(...) 		hack_st_read_fully(__VA_ARGS__)


#endif /* ST_HACK_H_ */
